﻿using Swifter.Api;
using Swifter.Data;
using Swifter.Json;
using Swifter.RW;
using Swifter.Tools;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    public sealed class HttpApplication : BaseApplication
    {
        public const int MinCacheCapacity = 65536;

        public static readonly HGlobalCachePool<byte> HGCaches = new HGlobalCachePool<byte>();


        public override void Log(string title, string msg)
        {
            Console.WriteLine($"[{DateTime.Now}] {title} : {msg}");
        }

        public HttpApplication(string name, string guid) : base(name, Guid.Parse(guid), 5000, 50000000)
        {
        }

        public void LoopByHttpListener(string host, int httpPort, int httpsPort)
        {
            if (!HttpListener.IsSupported)
            {
                throw new Exception("The current operating system does not support to use the HttpListener class.");
            }

            var httpUrl = $"http://{host}:{httpPort}/";
            var httpsUrl = $"https://{host}:{httpsPort}/";

            AddUrl("http", httpUrl);
            AddUrl("https", httpsUrl);

            using var httpListener = new HttpListener();

            httpListener.Prefixes.Add(httpUrl);

            Log(nameof(HttpApplication), "Added prefixe : " + httpUrl);

            httpListener.Prefixes.Add(httpsUrl);

            Log(nameof(HttpApplication), "Added prefixe : " + httpsUrl);

            try
            {
                httpListener.Start();
            }
            catch (Exception e)
            {
                Log(nameof(Exception), e.ToString());

                throw new Exception("The http(s) listener start failed, try restart program as administrator.");
            }

            Log(nameof(HttpApplication), "Http listener is start.");

            var runing = true;

            while (runing)
            {
                try
                {
                    if (!httpListener.IsListening)
                    {
                        httpListener.Start();
                    }

                    var httpListenerContext = httpListener.GetContext();

                    Task.Run(() =>
                    {
                        ThreadProcess(httpListenerContext);
                    });
                }
                catch (Exception e)
                {
                    if (runing)
                    {
                        Log(nameof(Exception), e.ToString());
                    }
                }
            }

            Log(nameof(HttpApplication), "Http listener is stop.");
        }

        public void LoopBySocket(IPAddress address, int port)
        {
            using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            socket.Bind(new IPEndPoint(address, port));

            socket.Listen(10000);

            Log(nameof(HttpApplication), "Socket is start.");

            var runing = true;

            while (runing)
            {
                try
                {
                    var socketContext = socket.Accept();

                    Task.Run(() =>
                    {
                        ThreadProcess(socketContext);
                    });
                }
                catch (Exception e)
                {
                    if (runing)
                    {
                        Log(nameof(Exception), e.ToString());
                    }
                }
            }

            Log(nameof(HttpApplication), "Socket is stop.");
        }

        private async void ThreadProcess(HttpListenerContext httpListenerContext)
        {
            try
            {
                var request = httpListenerContext.Request;
                var response = httpListenerContext.Response;

                response.AddHeader("Access-Control-Allow-Origin", "*");
                response.AddHeader("Access-Control-Allow-Headers", "Content-Type");
                response.AddHeader("Access-Control-Allow-Methods", request.HttpMethod);

                var encoding = Encoding.UTF8;

                var contentType = request.ContentType;

                using var inputStream = request.InputStream;

                var parameters = (contentType ?? "") switch
                {
                    "" => await @null(),
                    "application/json" => await json(),
                    "application/x-www-form-urlencoded" => await urlencoded(),
                    _ => throw new NotSupportedException("The content-type is not supported."),
                };

                var queryString = request.Url.Query;

                if (!string.IsNullOrEmpty(queryString))
                {
                    Urlencoded.FillFromString(queryString, true, Encoding.UTF8, RWHelper.CreateWriter(parameters).As<string>());
                }

                if (string.IsNullOrEmpty(parameters.Command))
                {
                    using (response.OutputStream)
                    {
                        response.ContentType = "text/plain";
                        response.ContentLength64 = 0;
                    }

                    return;
                }

                parameters.CommandParameters ??= new Dictionary<string, ValueCopyer>();

                parameters.Tag = httpListenerContext;
                parameters.IP = httpListenerContext.Request.RemoteEndPoint.Address.ToString();

                Process(parameters);

                static async Task<ContextParameters> @null()
                {
                    return await Task.FromResult(new ContextParameters());
                }

                async Task<ContextParameters> json()
                {
                    return await JsonFormatter.DeserializeObjectAsync<ContextParameters>(inputStream, encoding);
                }

                async Task<ContextParameters> urlencoded()
                {
                    using var reader = new StreamReader(inputStream, encoding);

                    var text = await reader.ReadToEndAsync();

                    var parameters = new ContextParameters();

                    Urlencoded.FillFromString(text, true, encoding, RWHelper.CreateWriter(parameters).As<string>());

                    return parameters;
                }
            }
            catch (Exception e)
            {
                httpListenerContext.Response.StatusCode = 500;

                using var writer = new StreamWriter(httpListenerContext.Response.OutputStream, Encoding.UTF8);

                writer.Write(e.Message);

                Log(nameof(HttpApplication), e.ToString());
            }
        }

        private async void ThreadProcess(Socket socket)
        {
            var hGCache = HGCaches.Rent();

            if (hGCache.Capacity < MinCacheCapacity)
            {
                hGCache.Expand(MinCacheCapacity);
            }

            hGCache.Count = await socket.ReceiveAsync(hGCache, SocketFlags.None);

            Console.WriteLine(Encoding.UTF8.GetString(hGCache.Context,0, hGCache.Count));

            await socket.SendAsync(hGCache, SocketFlags.None);

            HGCaches.Return(hGCache);
        }

        protected override void Finish(ContextInfo contextInfo, ContextParameters parameters, ContextResults results)
        {
            try
            {
                var httpListenerContext = (HttpListenerContext)parameters.Tag;

                var request = httpListenerContext.Request;
                var response = httpListenerContext.Response;

                switch (parameters.ContentType)
                {
                    case "Json":
                        json();
                        break;
                    default:
                        throw new NotSupportedException($"ContentType : '{parameters.ContentType}' is not supported.");
                }

                async void json()
                {
                    try
                    {
                        var encoding = Encoding.UTF8;

                        response.ContentType = $"text/json; charset={encoding.HeaderName}";

                        var hGCache = HGCaches.Rent();

                        JsonFormatter.SerializeObject(results, hGCache, encoding);

                        using var outputStream = response.OutputStream;

                        response.ContentLength64 = hGCache.Count;

                        await hGCache.WriteToAsync(outputStream);

                        HGCaches.Return(hGCache);
                    }
                    catch (Exception e)
                    {
                        Log(nameof(HttpApplication), e.Message);
                    }
                }
            }
            catch (Exception e)
            {
                Log(nameof(HttpApplication), e.Message);
            }
        }

        protected override ValueCopyer GetApplicationParameter(string name, ContextInfo contextInfo, ContextParameters parameters)
        {
            throw new NotImplementedException();
        }

        protected override ValueCopyer GetGlobalParameter(string name)
        {
            return name switch
            {
                "Command_Release_Version" => ValueCopyer.ValueOf("1.0.0"),
                "Default_Role_Id" => ValueCopyer.ValueOf(0),
                "Resource_Directory_Path" => ValueCopyer.ValueOf("D://Resources"),
                _ => throw new DeveloperException($"Global parameter : {name} not found."),
            };
        }

        protected override string[] GetRoleCommands(int roleId)
        {
            throw new NotImplementedException();
        }

        protected override void GetTriggers(string commandName, out string[] beforeTriggers, out string[] afterTriggers)
        {
            beforeTriggers = new string[0];
            afterTriggers = new string[0];
        }

        protected override void Initialize()
        {
            AddCommandProcessor("DB_MSSQL_TAXI", new SqlCommandProcessor(new Database("DB_MSSQL_TAXI")));
        }

        protected override CommandInfo TryGetCommand(string name, Version version)
        {
            if (name == "ReadAllAuthorizedMenus")
            {
                return new CommandInfo()
                {
                    Name = "ReadAllAuthorizedMenus",
                    Extended = new Dictionary<string, ValueCopyer> { { "Sql", ValueCopyer.ValueOf("SELECT * FROM Users") } },
                    Flags = CommandFlags.Select,
                    Id = 1,
                    Type = "DB_MSSQL_TAXI",
                    Parameters = new List<CommandInfo.ParameterInfo>(),
                    Results = new List<CommandInfo.ResultInfo>(),
                    Version = Version.Parse("1.0.0"),
                    CacheKey = "Users"
                };
            }

            return null;
        }

        protected override ApplicationState[] GetApplicationStates()
        {
            return new ApplicationState[0];
        }

        protected override void UpdateApplicationState(ApplicationState state)
        {
        }
    }
}
